using System;
using System.Collections.Generic;
using System.Linq;

using MathSharp;
using MathSharp.Script;

namespace MathSharp.Utils
{
	public class ParsableStringReader
	{
		string str;
		int offset;
		Namespace ns;
		
		readonly static char[] whitespaceChars = new char[] { ' ', '\t', '\r', '\n' };
		
		public ParsableStringReader(Namespace ns, string str) {
			offset = 0;
			this.ns = ns;
		}
		
		public ParsableStringReader(Namespace ns, string str, int offset): this(str) {
			Offset = offset;
			this.ns = ns;
		}
		
		public int Offset {
			get {
				return offset;
			}
			set {
				if (value >= str.Length())
					throw new ArgumentException("Устанавливаемая позиция разбора больше длинны строки");
				offset = value;
			}
		}
		
		public char CurrentChar {
			get {
				if (EOF)
					return '\x00';
				else
					return str[offset];
			}
		}
		
		public int FindNextAny(params char[] chars) {
			return str.IndexOfAny(chars, offset);
		}
		
		public string Read(int charsCount) {
			string res = str.Substring(offset, charsCount);
			offset += charsCount;
			return res;
		}
		
		public string ReadToEnd() {
			string res = str.Substring(offset);
			offset = str.Length();
			return res;
		}
		
		public string ReadTo(params char[] chars) {
			int pos = FindNextAny(chars);
			return (pos < 0)?ReadToEnd():Read(pos - offset);
		}
		
		public string ReadWhile(Predicate<char> predicate) {
			string res = "";
			while (!EOF && predicate(CurrentChar)) {
				res += CurrentChar;
				Seek(1);
			}
			return res;
		}

		public string ReadWhile(Predicate<char, int> predicate) {
			string res = "";
			int pos = 0;
			while (!EOF && predicate(CurrentChar, pos)) {
				res += CurrentChar;
				Seek(1);
				pos++;
			}
			return res;
		}
		
		public string ReadToWhitespace() {
			return ReadTo(whitespaceChars);
		}
		
		public string Peek(int charsCount) {
			if (charsCount + offset > str.Length) // запросили больше, чем есть
				return str.Substring(offset); // отдаём до конца строки
			else
				return str.Substring(offset, charsCount);
		}
		
		public string Seek(int seekOffset) {
			Offset += seekOffset;
		}
		
		public int SkipWhitespaceChars() {
			int initOffset = offset;
			while (!EOF && whitespaceChars.Contains(str[offset]))
				offset++;
			return offset - initOffset;
		}
		
		public bool EOF {
			get {
				return offset >= str.Length;
			}
		}
		
		public MathSharp.Script.Script Script {
			get {
				return ns.Script;
			}
		}
		
		public Namespace Namesapce {
			get {
				return ns;
			}
		}
		
		public string ReadStringDeclaration() {
			Action<string> error = delegate(string message) {
				throw new ParseException(null, 0, offset, 0x0, message);
			};
			
			if (EOF)
				error("Невозможно прочитать значение строки: обноружен конец строки");
			bool hardModeString = false;
			if (str[offset] == '@') {
				hardModeString = true;
				offset++;
			}
			if (EOF)
				error("Невозможно прочитать значение строки: обноружен конец строки");
			if (str[offset] != '"')
				error("Невозможно прочитать значение строки: объявление строки должно начинатся с символа \"");
			offset++;
			
			string res = "";
			bool stringIsEnd = false;
			while (!EOF) {
				char currentChar;
				if (!hardModeString && str[offset] == '\\') { // управляющие символы, либо символы записаные через код
					offset++;
					
					switch (str[offset]) {
						case '\'':
						case '"':
						case '\\':
						case '0':
						case 'a':
						case 'b':
						case 'f':
						case 'n':
						case 'r':
						case 't':
						case 'v':
							currentChar = char.Parse("\\{0}".format(str[offset]));
							break;				
						/*case 'u': // какая-то не ясная с ними ситуация
							currentChar = char.Parse("\\u{0}".format(str.Substring(offset+1, 4)));
							offset += 4;
							break;
						case 'U':
							break;*/
						case 'x':
							string hexVal = "";
							while (hexVal.Length < 4 && !EOF && "0123456789abcdefABCDEF".IndexOf(str[offset]) >= 0) {
								hexVal += str[offset];
								offset++;
							}
							currentChar = char.Parse("\\x{0}", hexVal);
							break;
						default:
							error("В строке указана неверный эскейп-код: \\{0}".format(str[offset]));
					}
				} else if (str[offset] == "\"") { // это либо конец строки, либо запись вида @"кавычка в строке - "" и не эскейп"
					if (hardModeString && (offset+1) < str.Length && str[offset+1] == "\"") // кавычка в строгой строке
						currentChar = '"';
					else {
						stringIsEnd = true;
						break;
					}
				} else
					currentChar = str[offset];
				
				res += currentChar;
			}
			
			if (!stringIsEnd)
				error("Неожиданое окончание строки. Не хватает закрывающей кавычки.");
			
			if (!EOF)
				offset++; // сдвигаем позицию за описание строки
			
			return res;
		}
		
		enum NumericReadState {
			BasePart,
			ExponentPart,
			HexNumeric
		}
		
		/// <summary>
		/// Возвращает int или double в зависимости от описания
		/// </summary>
		public object ReadNumericDeclaration() {
			Action<string> error = delegate(string message) {
				throw new ParseException(null, 0, offset, 0x0, message);
			};
			 
			if (EOF)
				error("Невозможно прочитать значение числа: обноружен конец строки");

			string numericPart = "";
			string exponentPart = "";
			NumericReadState state = ParsableStringReader.NumericReadState.BasePart;
			bool endNumeric = false;
			while (!EOF & endNumeric) {
				char currentChar = str[offset];
				
				switch (state) {
					case NumericReadState.BasePart:
						if (currentChar >= '0' && currentChar <= '9') {
							numericPart += currentChar;
						} else if (currentChar == '.') {
							if (numericPart.Contains('.'))
								error("Не коректный синтаксис числа: встретилась вторая десятичная запятая");
							else
								numericPart += '.';
						} else if (currentChar == 'E' || currentChar == 'e') { // метка начала экспоненты
							state = ParsableStringReader.NumericReadState.ExponentPart;
							offset++;
							continue; // принудительно переходим к следующему симсволу
						} else if (currentChar == 'x' || currentChar == 'X') { // метка шеснадцатитиричного числа
							if (numericPart != "0") // формат шеснадцатитиричного числа должен начинатся с 0x
								error("Неожидаемый символ {0} в описании числа", currentChar);
							state = ParsableStringReader.NumericReadState.HexNumeric;
							offset++;
							continue; // принудительно переходим к следующему симсволу
						} else {
							endNumeric = true;
							continue;
						}
						break;
					case NumericReadState.ExponentPart:
						if (currentChar >= '0' && currentChar <= '9') {
							exponentPart += currentChar;
						} else if (currentChar == '.') {
							if (numericPart.Contains('.'))
								error("Не коректный синтаксис числа: встретилась вторая десятичная запятая");
							else
								exponentPart += '.';
						} else {
							endNumeric = true;
							continue;
						}
						break;
					case NumericReadState.HexNumeric:
						if ("01234567890abcdefABCDEF".Contains(currentChar))
							numericPart += currentChar;
						else {
							endNumeric = true;
							continue;
						}
						break;
					default:
						throw new Exception("Неизвестное состояние обработки числа");
						break;
				}
				
				offset++;
			}
			
			return null;
		}
	}
}
